package com.matthewmadson.util.regex;

import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Matthew Madson
 * @author Patrick Chan
 * @see http://www.exampledepot.com/egs/java.util.regex/Tokenize.html
 */
public class RegexTokenizer
	implements Iterator<Token>, Iterable<Token>
{
	/** 
	 * Holds the original input to search for tokens
	 */
	private CharSequence input;

	/**
	 * Used to find tokens
	 */
	private Matcher matcher;

	/**
	 * If true, the String between tokens are returned
	 */
	private boolean returnDelims;

	/**
	 *  The current delimiter value. If non-null, should be returned
	 * 	at the next call to next()
	 */
	private String delim;

	/**
	 * The current matched value. If non-null and delim=null,
	 * should be returned at the next call to next()
	 */
	private String match;

	/**
	 * The value of matcher.end() from the last successful match.
	 */
	private int lastEnd = 0;

	/**
	 * If returnDelims is <code>true</code>, the text between
	 * matching tokens is also returned and the
	 * tokens are returned in the following sequence - delimiter, token,
	 * delimiter, token ... 
	 * 
	 * <p>
	 * Tokens can never be an empty string but delimiters may.
	 * 
	 * @param input The string to be tokenized
	 * @param patternStr The regular expression pattern that identifies tokens
	 * @param returnDelims if <code>true</code> return delimiter strings with token strings
	 */
	public RegexTokenizer(final CharSequence input,
		final String patternStr,
		final boolean returnDelims)
	{
		this.input = input;
		this.returnDelims = returnDelims;

		Pattern pattern = Pattern.compile(patternStr);
		matcher = pattern.matcher(input);
	}

	/**
	 * 
	 */
	public boolean hasNext()
	{
		if(matcher == null)
		{
			return false;
		}
		if(delim != null || match != null)
		{
			return true;
		}
		if(matcher.find())
		{
			if(returnDelims)
			{
				delim = input.subSequence(lastEnd, matcher.start()).toString();
			}
			match = matcher.group();
			lastEnd = matcher.end();
		}
		else if(returnDelims && lastEnd < input.length())
		{
			delim = input.subSequence(lastEnd, input.length()).toString();
			lastEnd = input.length();
			matcher = null;
		}
		return delim != null || match != null;
	}

	/**
	 * Returns the next token (or delimiter if returnDelims is true).
	 */
	public Token next()
	{
		Token result = null;

		if(delim != null)
		{
			result = new Token(delim, TokenType.DELIM);
			delim = null;
		}
		else if(match != null)
		{
			result = new Token(match, TokenType.TOKEN);
			match = null;
		}
		
		return result;
	}

	/**
	 * @return <code>true</code> if the call to next() will return a token <code>false</code> otherwise
	 */
	public boolean isNextToken()
	{
		return delim == null && match != null;
	}

	/**
	 * @throws UnsupportedOperationException
	 */
	public void remove()
	{
		throw new UnsupportedOperationException();
	}

	
	/**
	 * 
	 */
	public Iterator<Token> iterator()
	{
		return this;
	}
	
	/**
	 * 
	 * @return The current instance as an iterable to allow for foreach iteration
	 */
	public Iterable<Token> getTokens()
	{
		return this;
	}
}